package com.xiashitech.agent.instrumentation.apachehttpclient.v4.inst;

import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.xiashitech.agent.cache.status.CacheStatus;
import com.xiashitech.agent.instrumentation.bridge.Java8Bridge;
import com.xiashitech.agent.utils.DurationPrinter;
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
import io.opentelemetry.javaagent.shaded.io.opentelemetry.api.baggage.Baggage;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;
import org.apache.http.HttpEntity;
import org.apache.http.HttpHost;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.client.ResponseHandler;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.entity.BufferedHttpEntity;
import org.apache.http.util.EntityUtils;

import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.hasClassesNamed;
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.implementsInterface;
import static net.bytebuddy.matcher.ElementMatchers.*;

public class ApacheHttpClientv4Instrumentation implements TypeInstrumentation {
    @Override
    public ElementMatcher<ClassLoader> classLoaderOptimization() {
        return hasClassesNamed("org.apache.http.client.HttpClient");
    }

    @Override
    public ElementMatcher<TypeDescription> typeMatcher() {
        return implementsInterface(named("org.apache.http.client.HttpClient"));
    }

    @Override
    public void transform(TypeTransformer transformer) {
        transformer.applyAdviceToMethod(
                isMethod()
                        .and(named("execute"))
                        .and(not(isAbstract()))
                        .and(takesArguments(1))
                        .and(takesArgument(0, named("org.apache.http.client.methods.HttpUriRequest"))),
                this.getClass().getName() + "$UriRequestAdvice");

        transformer.applyAdviceToMethod(
                isMethod()
                        .and(named("execute"))
                        .and(not(isAbstract()))
                        .and(takesArguments(2))
                        .and(takesArgument(0, named("org.apache.http.client.methods.HttpUriRequest")))
                        .and(takesArgument(1, named("org.apache.http.protocol.HttpContext"))),
                this.getClass().getName() + "$UriRequestAdvice");

        transformer.applyAdviceToMethod(
                isMethod()
                        .and(named("execute"))
                        .and(not(isAbstract()))
                        .and(takesArguments(2))
                        .and(takesArgument(0, named("org.apache.http.client.methods.HttpUriRequest")))
                        .and(takesArgument(1, named("org.apache.http.client.ResponseHandler"))),
                this.getClass().getName() + "$UriRequestWithHandlerAdvice");

        transformer.applyAdviceToMethod(
                isMethod()
                        .and(named("execute"))
                        .and(not(isAbstract()))
                        .and(takesArguments(3))
                        .and(takesArgument(0, named("org.apache.http.client.methods.HttpUriRequest")))
                        .and(takesArgument(1, named("org.apache.http.client.ResponseHandler")))
                        .and(takesArgument(2, named("org.apache.http.protocol.HttpContext"))),
                this.getClass().getName() + "$UriRequestWithHandlerAdvice");

        transformer.applyAdviceToMethod(
                isMethod()
                        .and(named("execute"))
                        .and(not(isAbstract()))
                        .and(takesArguments(2))
                        .and(takesArgument(0, named("org.apache.http.HttpHost")))
                        .and(takesArgument(1, named("org.apache.http.HttpRequest"))),
                this.getClass().getName() + "$RequestAdvice");

        transformer.applyAdviceToMethod(
                isMethod()
                        .and(named("execute"))
                        .and(not(isAbstract()))
                        .and(takesArguments(3))
                        .and(takesArgument(0, named("org.apache.http.HttpHost")))
                        .and(takesArgument(1, named("org.apache.http.HttpRequest")))
                        .and(takesArgument(2, named("org.apache.http.protocol.HttpContext"))),
                this.getClass().getName() + "$RequestAdvice");

        transformer.applyAdviceToMethod(
                isMethod()
                        .and(named("execute"))
                        .and(not(isAbstract()))
                        .and(takesArguments(3))
                        .and(takesArgument(0, named("org.apache.http.HttpHost")))
                        .and(takesArgument(1, named("org.apache.http.HttpRequest")))
                        .and(takesArgument(2, named("org.apache.http.client.ResponseHandler"))),
                this.getClass().getName() + "$RequestWithHandlerAdvice");

        transformer.applyAdviceToMethod(
                isMethod()
                        .and(named("execute"))
                        .and(not(isAbstract()))
                        .and(takesArguments(4))
                        .and(takesArgument(0, named("org.apache.http.HttpHost")))
                        .and(takesArgument(1, named("org.apache.http.HttpRequest")))
                        .and(takesArgument(2, named("org.apache.http.client.ResponseHandler")))
                        .and(takesArgument(3, named("org.apache.http.protocol.HttpContext"))),
                this.getClass().getName() + "$RequestWithHandlerAdvice");
    }

    @SuppressWarnings("unused")
    public static class UriRequestAdvice {

        @Advice.OnMethodEnter(suppress = Throwable.class)
        public static void methodEnter(
                @Advice.Argument(0) HttpUriRequest request) {
          /*  System.out.println("###httpclient001");
            ApacheHttpClientHandler.handle(request);
            System.out.println("###HttpClient url enter:"+request.getURI());
            System.out.println("###httpclient010");*/
            DurationPrinter durationPrinter = new DurationPrinter("ApacheHttpClientv4Instrumentation");
            if (CacheStatus.cannotRun()) {
                return;
            }
           /* Java8Bridge.currentForBaggage().toBuilder()
                    .put("xs.userId", "39")
                    .build().makeCurrent();*/
            durationPrinter.printElapseDuration("UriRequestAdvice b");
        }

        @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
        public static void methodExit(
                @Advice.Argument(0) HttpUriRequest request,
                @Advice.Return Object result) {
            DurationPrinter durationPrinter = new DurationPrinter("ApacheHttpClientv4Instrumentation");
            if (CacheStatus.cannotRun()) {
                return;
            }
            System.out.println("###HttpClient url:" + request.getURI());

            String url = String.valueOf(request.getURI());
            try {

               //  XSDubboLt27Handler.HttpClientHandle(request, result);

                if (url.contains("/profile_platform/api/v2/userInfo")) {
                    HttpEntity entity =  ((HttpResponse) result).getEntity();
                    entity = new BufferedHttpEntity(entity);
                    ((HttpResponse) result).setEntity(entity);
                    String str = EntityUtils.toString(entity);

                    System.out.println("###############str"+str);
                    JSONObject resultObj = JSONUtil.parseObj(str);
                    String data = resultObj.get("data").toString();
                    System.out.println("###############data"+data);
                    JSONObject userObj = JSONUtil.parseObj(data);
                    String userId = userObj.get("id").toString();
                    System.out.println("###############userId"+userId);
                    String username = userObj.get("username").toString();
//                    Java8Bridge.currentForBaggage().toBuilder()
//                            .put("xs.userId", userId)
//                            .put("xs.loginName", username)
//                            .build().makeCurrent();
                    Java8Bridge.currentForTraceContextVars().set("xs.userId", userId).set("xs.loginName", username);
                    System.out.println("############thread1"+Thread.currentThread().getId());
                    Java8Bridge.setUserIdToXSBaggage(userId);
                }

            } catch (Throwable e) {
                e.printStackTrace();
            }
            ApacheHttpClientHelper.dealReentryValue(result);
            Baggage baggage = Java8Bridge.currentForBaggage();
            System.out.println("###httpclient baggage " +baggage);
            durationPrinter.printElapseDuration("UriRequestAdvice e");
        }
    }

    @SuppressWarnings("unused")
    public static class UriRequestWithHandlerAdvice {

        @Advice.OnMethodEnter(suppress = Throwable.class)
        public static void methodEnter(
                @Advice.Argument(0) HttpUriRequest request,
                @Advice.Argument(value = 1, readOnly = false) ResponseHandler<?> handler) {
            System.out.println("###httpclient002");
            DurationPrinter durationPrinter = new DurationPrinter("ApacheHttpClientv4Instrumentation");
            if (CacheStatus.cannotRun()) {
                return;
            }
            //ApacheHttpClientHandler.handle(request, handler);
            // Wrap the handler to deal ReentryValue
            if (handler != null) {
                handler = new WrappingReentryValueResponseHandler<>(handler);
            }
            durationPrinter.printElapseDuration("UriRequestWithHandlerAdvice b");
        }

        @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
        public static void methodExit(
                @Advice.Argument(0) HttpUriRequest request,
                @Advice.Return Object result) {
          //  XSDubboLt27Handler.HttpClientHandle(request, result);
            DurationPrinter durationPrinter = new DurationPrinter("ApacheHttpClientv4Instrumentation");
            if (CacheStatus.cannotRun()) {
                return;
            }
            ApacheHttpClientHelper.dealReentryValue(result);
            durationPrinter.printElapseDuration("UriRequestWithHandlerAdvice e");
        }
    }

    @SuppressWarnings("unused")
    public static class RequestAdvice {

        @Advice.OnMethodEnter(suppress = Throwable.class)
        public static void methodEnter(
                @Advice.Argument(0) HttpHost host,
                @Advice.Argument(1) HttpRequest request) {
            DurationPrinter durationPrinter = new DurationPrinter("ApacheHttpClientv4Instrumentation");
            System.out.println("###httpclient003");
            if (CacheStatus.cannotRun()) {
                return;
            }
            durationPrinter.printElapseDuration("RequestAdvice b");
        }

        @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
        public static void methodExit(@Advice.Return Object result) {
            DurationPrinter durationPrinter = new DurationPrinter("ApacheHttpClientv4Instrumentation");
            if (CacheStatus.cannotRun()) {
                return;
            }
            ApacheHttpClientHelper.dealReentryValue(result);
            durationPrinter.printElapseDuration("RequestAdvice e");
        }
    }

    @SuppressWarnings("unused")
    public static class RequestWithHandlerAdvice {

        @Advice.OnMethodEnter(suppress = Throwable.class)
        public static void methodEnter(
                @Advice.Argument(0) HttpHost host,
                @Advice.Argument(1) HttpRequest request,
                @Advice.Argument(value = 2, readOnly = false) ResponseHandler<?> handler) {
            DurationPrinter durationPrinter = new DurationPrinter("ApacheHttpClientv4Instrumentation");
            System.out.println("###httpclient003");

            if (CacheStatus.cannotRun()) {
                return;
            }
            // Wrap the handler to deal ReentryValue
            if (handler != null) {
                handler = new WrappingReentryValueResponseHandler<>(handler);
            }
            durationPrinter.printElapseDuration("RequestWithHandlerAdvice b");
        }

        @Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
        public static void methodExit(@Advice.Return Object result) {
            DurationPrinter durationPrinter = new DurationPrinter("ApacheHttpClientv4Instrumentation");
            if (CacheStatus.cannotRun()) {
                return;
            }
            ApacheHttpClientHelper.dealReentryValue(result);
            durationPrinter.printElapseDuration("RequestWithHandlerAdvice e");
        }
    }
}
